import gtk, math, Point, getopt, EntryDialog, copy

class Shape:
	
	def __init__(self, window, spec):
		self.shapes = []	# a list of shapes (composite pattern)
		self.window = window
		self.gc = window.new_gc()
		spec.intensity = 50000 # acochambracao para conseguir cores diferentes na hora de selecionar
		self.set_up_gc(spec)
		self.finished = False
		self.all_points = [] 	# fill during the first time drawing,border points
		self.filled= False
		self.inside_points=[]  	# fill during the first time drawing,inside points

	def set_up_gc(self, spec):
		#If not specified self.trace will not change
		self.trace = 0 if spec.trace == "Normal" else 1 if spec.trace == "Dotted" else 2 if spec.trace == "Dashed" else self.trace
		self.color = spec.color
		(red,green,blue) = (spec.intensity*x for x in spec.color)
		self.double = spec.double
		self.gc.set_rgb_fg_color(gtk.gdk.Color(red,green,blue))

	def draw(self, save, w):
		for s in self.shapes:
			s.draw(save, w)
	
	def points_in_window(self,points,w):
		return [x for x in points if x[0]>=w[0] and x[0]<=w[2] and x[1]>=w[1] and x[1]<=w[3]]

	def translate(self, dx, dy):
		for s in self.shapes:
			s.translate(dx,dy)
			
	def rotate(self,save,angle,center):
		for s in self.shapes:
			s.rotate(save,angle,center)
	
	def reflect(self,save,point1,point2):
		for s in self.shapes:
			s.reflect(save,point1,point2)

	def simple_reflect(self):
		for s in self.shapes:
			s.simple_reflect()	
		
	def fill(self,gc):
		for s in self.shapes:
			s.fill(gc)
		
	def group(self):
		pass
	
	def ungroup(self):
		pass

	def print_spec(self, arq):
		arq.write(str(self.spec))
				
	def hasPoint(self, point):
		for s in self.shapes:
			if s.hasPoint(point):
				return True
		return False
			
	def change_gc(self,spec, selecting = False):
		for s in self.shapes:
			s.change_gc(spec, selecting)
	
	#Call it only from inside de select method
	def back_old_gc(self):
		for s in self.shapes:
			s.back_old_gc()

	def clear(self):
		for s in self.shapes:
			s.clear()

#############################################################################
#############################################################################
class Line(Shape):
	
	def __init__(self, point1, point2, window, spec):
		Shape.__init__(self, window, spec)
		self.point1 = point1
		self.point2 = point2
		self.spec = spec
		self.to_save = (spec, self.point1, self.point2)
	
	def __prepare_to_draw(self):
		steep = abs(self.point1.y-self.point2.y)>abs(self.point1.x-self.point2.x)
		#swap the points		
		if steep:
			self.point1.y,self.point1.x,self.point2.y,self.point2.x = self.point1.x,self.point1.y,self.point2.x,self.point2.y
	
		if self.point1.x>self.point2.x:
			self.point1,self.point2 = self.point2,self.point1
		deltax = self.point2.x-self.point1.x
		deltay = abs(self.point2.y-self.point1.y)
		return (steep,deltax,deltay,deltax//2,self.point1.y,1 if self.point1.y<self.point2.y else -1)

	def generate_points(self):	
		if len(self.all_points)==0:
			(steep,deltax,deltay,erro,y,ystep)=self.__prepare_to_draw()
			for x in range(self.point1.x,self.point2.x+1):
				self.all_points.append((y,x)) if steep else self.all_points.append((x,y))
				erro-=deltay
				if erro<0:
					y+=ystep
					erro+=deltax 
			if self.trace == 1:
				init = self.all_points[0]
				last = self.all_points[-1:]
				self.all_points = [x for x in self.all_points if (x[steep]-init[steep])%2==0]
				self.all_points+=last
			elif self.trace == 2:
				init = self.all_points[0]
				last = self.all_points[-2:]
				self.all_points = [x for x in self.all_points if (x[steep]-init[steep])%5<3]
				self.all_points +=last
										
			if self.double:
				if steep:
					self.all_points = self.all_points + [ (p[0]+1,p[1]) for p in self.all_points ]
				else:
					self.all_points = self.all_points + [ (p[0],p[1]+1) for p in self.all_points ]
			if steep:
				self.point1.y,self.point1.x,self.point2.y,self.point2.x = self.point1.x,self.point1.y,self.point2.x,self.point2.y

	def draw(self,save,w):
		self.generate_points()		
		self.window.draw_points(self.gc,self.points_in_window(self.all_points,w))		
		self.finished = True
		return self.finished

	def hasPoint(self, point):
		x, y = point.x, point.y
		point = (x,y)
		for p in self.all_points:
			if (point[0]-p[0])**2 + (point[1]-p[1])**2 < 25 :
				return True
		return False

	def translate(self,dx,dy):
		self.all_points=[]
		self.point1.translate(dx,dy)
		self.point2.translate(dx,dy)
		
	def rotate(self,save,angle,center):
		self.all_points=[]
		self.point1.rotate(angle,center)
		self.point2.rotate(angle,center)
	
	def change_gc(self,spec, selecting = False):
		if not selecting:
			spec.intensity = 50000
		self.old_gc = self.gc
		self.old_trace = copy.copy(self.trace)
		self.old_color = copy.copy(self.color)
		self.old_double = copy.copy(self.double)
		self.clear()
		self.gc = self.window.new_gc() #gc is not copyable
		self.set_up_gc(spec)

		#Call it only from inside de select method
	def back_old_gc(self):
		self.gc = self.old_gc
		self.trace = self.old_trace
		self.color = self.old_color
		self.double = self.old_double
		self.clear()
	
	def clear(self):
		self.all_points = []
		self.finished = False

	
	def simple_reflect(self):
		self.point1.y, self.point2.y = -self.point1.y, -self.point2.y
		self.generate_points()

#########################################################################
#########################################################################
class Circle(Shape):

	def __init__(self, center, point, window, spec, save):
		Shape.__init__(self, window, spec)
		self.center = center
		self.init_point = point
		self.last = Point.Point(0,0)
		self.angle = self.get_angle(save)*math.pi/180;
		self.to_save = (spec, self.center.x, self.center.y, self.init_point.x, self.init_point.y, self.angle)
		self.save = save
		
	def get_angle(self, save):
		if save:
			win = EntryDialog.EntryDialog("Angulo (em graus): ", "360", True)
			win.show()
			gtk.main()
			angle = int(win.ret)
		else:
			angle = 360
		return angle
		
	def generate_points(self):
		if not self.finished:
			#Preparing to draw
			radius = (self.center-self.init_point).norm()
			if radius < 0.01:
				return True
			sin, cos = math.sin(1/radius), math.cos(1/radius)
			angles = [x/radius for x in range(math.trunc(self.angle*radius))]
			lastx = self.init_point.x
			lasty = self.init_point.y
			self.all_points = []
			self.all_points.append((lastx, lasty))
			
			#drawing the normal traced circle
			for a in angles:
				x, y = lastx - self.center.x, lasty - self.center.y
				lastx,  lasty= self.center.x + cos*x + sin*y, self.center.y + cos*y - sin*x
				self.all_points.append((math.trunc(lastx),math.trunc(lasty)))
				self.last.x, self.last.y = math.trunc(lastx), math.trunc(lasty)
				if self.double:
					dx, dy = int(abs(x) > abs(y)), int(abs(x) <= abs(y))
					self.all_points.append((math.trunc(lastx + dx), math.trunc(lasty + dy)))

			#only fill if is saving
			if self.save :
				self.inside_points=[]
				for p in self.all_points:
					if(p[0]<self.center.x):
						for x in range(p[0]+1,2*self.center.x-p[0]):			
							self.inside_points.append((x,p[1]))
	
			if(self.trace != 0):
				aux = []
				for x in self.all_points:
					index = int(abs(x[0] - self.center.x) > abs(x[1] - self.center.y))
					if (self.trace == 1 and x[index]%2 == 0) or (self.trace==2 and x[index]%7 < 5):
						aux.append((x[0],x[1]))
				self.all_points = aux
			
			self.finished = True			
		return self.finished
	
	def draw(self, save,w):
		self.generate_points()
		if  self.finished and self.filled and self.angle == 2*math.pi and save:
			self.window.draw_points(self.fill_gc,self.points_in_window(self.inside_points,w))		
		self.window.draw_points(self.gc, self.points_in_window(self.all_points,w))
		return self.finished
			
	def fill(self,gc):
		if not self.filled:
			self.filled = True
			self.fill_gc = gc
			self.generate_points()

	def hasPoint(self, point):
		x, y = point.x, point.y
		point = (x,y)
		for p in self.all_points:
			if (point[0]-p[0])**2 + (point[1]-p[1])**2 < 25 :
				return True
		return False


	def translate(self,dx,dy):
		self.all_points = [(p[0]+dx,p[1]+dy) for p in self.all_points]
		self.inside_points = [(p[0]+dx,p[1]+dy) for p in self.inside_points]	
		self.center.translate(dx,dy)
		self.init_point.translate(dx,dy)
		self.last.translate(dx, dy)

	def rotate(self,save,angle,center):
		self.center.rotate(angle,center)
		self.init_point.rotate(angle,center)
		self.last.rotate(angle, center)
		self.finished = False
	
	def change_gc(self,spec, selecting = False):
		if not selecting:
			spec.intensity = 50000
		self.old_gc = self.gc
		self.old_trace = copy.copy(self.trace)
		self.old_color = copy.copy(self.color)
		self.old_double = copy.copy(self.double)
		self.clear()
		self.gc = self.window.new_gc() #gc is not copyable
		self.set_up_gc(spec)
	
		#Call it only from inside de select method
	def back_old_gc(self):
		self.gc = self.old_gc
		self.trace = self.old_trace
		self.color = self.old_color
		self.double = self.old_double
		self.clear()
	
	def clear(self):
		self.all_points = []
		self.finished = False

	def simple_reflect(self):	
		self.finished = False
		self.center.y = -self.center.y
		self.init_point.x, self.init_point.y = self.last.x, -self.last.y
				
#########################################################################
#########################################################################
class Polygon(Shape):

	def __init__(self, first_point, second_point, window, spec):
		Shape.__init__(self, window, spec)
		self.points = [first_point, second_point]
		self.spec = spec
		self.save = True
	
	def remove_last(self, remove):
		if(remove):
			self.points.pop()
			self.shapes.pop()
	
	def check_last_point(self, point):
		if (point - self.points[0]).norm() < 20:
			self.finished = True
			# create new instance?
			return Point.Point(self.points[1].x,self.points[1].y)
		else:
			return point
	
	def add_point(self, point, save):
		self.remove_last(not self.save)
		self.save = save
		if save:
			point = self.check_last_point(point)
		#needed because line change the order of the points 
		point1 = Point.Point(self.points[-1].x, self.points[-1].y)
		point2 = Point.Point(point.x, point.y)
		self.shapes.append(Line(point2, point1, self.window, self.spec))
		self.points.append(point)
		
		#better place?
		if self.finished:
			self.to_save = (self.spec, self.points)
	
	def draw(self, save,w):	
		if save and len(self.inside_points)>0:
			self.window.draw_points(self.fill_gc, self.points_in_window(self.inside_points,w))	
		Shape.draw(self, save, w)
		return self.finished

	def fill(self,gc):
		#arrumar tamanho
		if len(self.inside_points)!=0:
			return 
		self.fill_gc = gc
		for py in range(500):#arrumar
			node = []
			for i in range(1,len(self.points)-1):#percorre os pontos das linhas
				if(min(self.points[i].y,self.points[i+1].y)< py and py<=max(self.points[i].y,self.points[i+1].y)):
					node.append( (int) (self.points[i].x+(py-self.points[i].y+0.0)/(self.points[i+1].y-self.points[i].y+0.0)*(self.points[i+1].x-self.points[i].x)+0.5))
			node.sort()
			for i in range(0,len(node),2):
				if(i+1<len(node)):
					for px in range(node[i],node[i+1]+1):
						self.inside_points.append((px,py))

	def translate(self,dx,dy):
		for p in self.points:
			p.translate(dx,dy)
		Shape.translate(self, dx, dy)
		if len(self.inside_points)>0:
			self.inside_points = []
			self.fill(self.fill_gc)

	def rotate(self,save,angle,center):
		for p in self.points:
			p.rotate(angle,center)
		Shape.rotate(self, save, angle, center)
		if len(self.inside_points)>0:
			self.inside_points = []
			self.fill(self.fill_gc)

######################################################################################
######################################################################################
class Regular(Polygon):

	def __init__(self, first_point, second_point, window, spec):
		Shape.__init__(self, window, spec)
		self.center = first_point
		self.spec = spec
		self.size = self.get_size()
		self.points = self.generate_points(self.size, second_point)		
		self.shapes = self.get_lines()
		self.finished = True
	
	def get_size(self):
		size = 0
		while(size < 3 or size > 8):
			win = EntryDialog.EntryDialog("Qual o numero de lados (> 2, < 8): ", "3", True)
			win.show()
			gtk.main()
			size = int(win.ret)
		return size
	
	def generate_points(self, n, seed):
		p = seed - self.center
		points = [p] 
		origin = Point.Point(0,0)
		for m in range(n):
			p.rotate(2*math.pi/n, origin)
			points.append(Point.Point(p.x + self.center.x, p.y + self.center.y))
		points.append(Point.Point(points[1].x, points[1].y)) # mega monkey of polygon
		return points
	
	def get_lines(self):
		poly = Polygon(self.points[0], self.points[1], self.window, self.spec)
		for m in range(2,len(self.points)):
			poly.add_point(self.points[m], True)			
		return poly.shapes
				
#######################################################################################
#######################################################################################
class Arrow(Shape):

	def __init__(self, point1, point2, window, spec):
		Shape.__init__(self, window, spec)
		self.start = point1
		self.end = point2
		self.spec = spec
		self.shapes.append(Line(point1, point2, window, spec))
		
	def generate_points(self):
		center = Point.Point(self.start.x, self.start.y)
		vet = self.end - self.start
		self.side_size = vet.norm()/5
		angle = math.atan2(vet.y, vet.x)
		
		self.rotate(False, angle, center)
	
		p1 = Point.Point(self.end.x - self.side_size, self.end.y)
		p1.rotate(math.atan(0.3333), self.end)
		o1 = Point.Point(self.end.x, self.end.y)
		self.shapes.append(Line(o1, p1, self.window, self.spec))
		
		p2 = Point.Point(self.end.x - self.side_size, self.end.y)
		p2.rotate(math.atan(-0.3333), self.end)
		o2 = Point.Point(self.end.x, self.end.y)
		self.shapes.append(Line(o2, p2, self.window, self.spec))
		
		self.rotate(False, -angle, center)
	
	def draw(self,save,w):	
		if not self.finished:
			self.generate_points()
		self.finished = True
		Shape.draw(self, save, w)
		return self.finished
